﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using MsTestUtilities.Utilities;
using System;

namespace MsTestUtilities.RowTests
{
    public class Expectation<E> : IExpectation<E>
    {
        private Predicate<Func<E>> _expectationPredicate;
        private Func<E, string> _errorMessageGenerator;

        public Expectation(E expectation)
        {
            _expectationPredicate = actualGenerator => expectation.Equals(actualGenerator());
            _errorMessageGenerator = (actual) => string.Format("Expected <{0}>. Actual <{1}>.", expectation, actual);
        }

        public Expectation(Func<E> expectation)
        {
            _expectationPredicate = actualGenerator => expectation.Equals(actualGenerator());
            _errorMessageGenerator = (actual) => string.Format("Expected <{0}>. Actual <{1}>.", expectation, actual);
        }

        public Expectation(Predicate<E> expectation)
            : this(expectation, (actual) => string.Format("Expectation failed for <{0}>.", actual))
        {
        }

        public Expectation(Predicate<E> expectation, Func<E, string> errorMessageGenerator)
        {
            _expectationPredicate = fe => expectation(fe());
            _errorMessageGenerator = errorMessageGenerator;
        }

        public Expectation(Predicate<Func<E>> expectation, Func<E, string> errorMessageGenerator)
        {
            _expectationPredicate = expectation;
            _errorMessageGenerator = errorMessageGenerator;
        }

        public AssertionResult IsSatisfiedFor(Func<E> actualGenerator)
        {
            try
            {
                Setable<E> actual = new Setable<E>();
                Func<E> actualEvaluator = () =>
                {
                    actual.Value = actualGenerator();
                    return actual.Value;
                };

                var expectationSatisfied = _expectationPredicate(actualEvaluator);
                if (!expectationSatisfied)
                {
                    var failedExpectationMessage = _errorMessageGenerator(actual.Value);
                    return AssertionResult.Error(failedExpectationMessage);
                }

                return AssertionResult.Success;
            }
            catch (AssertFailedException ex)
            {
                return AssertionResult.Error(ex.Message);
            }
            catch (Exception ex)
            {                
                return AssertionResult.Error("An unexpected exception was trown: {0}", ex);
            }
        }
    }
}
